const std = @import("std");
const setting = @import("setting.zig");
const Point = @import("point.zig");

pub fn Shape(comptime T: type, comptime U: type) type {
    return struct {
        const Self = @This();

        id: u32,
        body_id: u32,
        prev_shape_id: u32,
        next_share_id: u32,
        sensor_index: u32,
        type: ShapeType,
        density: f32,
        friction: f32,
        restitution: f32,
        rolling_resistance: f32,
        tangent_speed: f32,
        user_material_id: u32,

        aabb: AABB,
        fat_aabb: AABB,
        local_centroid: Point,
        proxy_key: u32,

        filter: Filter,
        user_data: *U,
        custom_color: u32,

        shape: T,

        generation: u16,
        enable_sensor_event: bool,
        enable_contact_event: bool,
        enable_hit_event: bool,
        enable_pre_solve_event: bool,
        enlarged_aabb: bool,

        // pub fn computeAABB(self: *Self, transform: *Point.Transform) AABB {}

        pub fn getRadius(self: *Self) f32 {
            switch (self.type) {
                ShapeType.Circle, ShapeType.Capsule, ShapeType.Polygon => {
                    return self.shape.radius;
                },
                else => {
                    return 0.0;
                },
            }
        }
    };
}

/// Sensors are shapes that live in the broad-phase but never have contacts.
/// At the end of the time step all sensors are queried for overlap with any other shapes.
/// Sensors ignore body type and sleeping.
/// Sensors generate events when there is a new overlap or and overlap disappears.
/// The sensor overlaps don't get cleared until the next time step regardless of the overlapped
/// shapes being destroyed.
/// When a sensor is destroyed.
pub const SensorOverlaps = struct {
    overlaps: std.ArrayList(u32),
};

pub const ShapeExtent = struct {
    min_extent: f32,
    max_extent: f32,
};

pub const ChainShape = struct {
    id: u32,
    body_id: u32,
    next_chain_id: u32,
    count: u32,
    material_count: u32,
    shape_indices: *u32,
    materials: *SurfaceMaterial,
    generation: u16,
};

/// Surface materials allow chain shapes to have per segment surface properties.
pub const SurfaceMaterial = struct {
    /// The Coulomb (dry) friction coefficient, usually in the range [0,1].
    friction: f32,
    /// The coefficient of restitution (bounce) usually in the range [0,1].
    /// https://en.wikipedia.org/wiki/Coefficient_of_restitution
    restitution: f32,
    /// The rolling resistance usually in the range [0,1].
    rolling_resistance: f32,
    /// The tangent speed for conveyor belts
    tangent_speed: f32,
    /// User material identifier. This is passed with query results and to friction and restitution
    /// combining functions. It is not used internally.
    user_material_id: f32,
    /// Custom debug draw color.
    custom_color: u32,
};

/// A line segment with one-sided collision. Only collides on the right side.
/// Several of these are generated for a chain shape.
/// ghost1 -> point1 -> point2 -> ghost2
pub const ChainSegment = struct {
    /// The tail ghost vertex
    ghost1: Point,
    /// The line segment
    segment: Segment,
    /// The head ghost vertex
    ghost2: Point,
    /// The owning chain shape index (internal usage only)
    chain_id: u32,
};

/// A line segment with two-sided collision.
pub const Segment = struct {
    /// The first point
    point1: Point,
    /// The second point
    point2: Point,
};

/// A solid convex polygon. It is assumed that the interior of the polygon is to
/// the left of each edge.
/// Polygons have a maximum number of vertices equal to B2_MAX_POLYGON_VERTICES.
/// In most cases you should not need many vertices for a convex polygon.
/// @warning DO NOT fill this out manually, instead use a helper function like
/// b2MakePolygon or b2MakeBox.
pub const Polygon = struct {
    /// The polygon vertices
    vertices: [MaxPolygonVertices]Point,
    /// The outward normal vectors of the polygon sides
    normals: [MaxPolygonVertices]Point,
    /// The centroid of the polygon
    centroid: Point,
    /// The external radius for rounded polygons
    radius: f32,
    /// The number of polygon vertices
    count: u8,

    pub fn init(center1: Point, center2: Point, radius: f32) Capsule {
        return Capsule{
            .center1 = center1,
            .center2 = center2,
            .radius = radius,
        };
    }

    pub fn computeAABB(self: *Capsule, transform: *Point.Transform) AABB {
        var p1 = self.center1.transform2World(transform);
        var p2 = self.center2.transform2World(transform);

        var min = p1.newMinPoint(&p2);
        var max = p1.newMaxPoint(&p2);

        const lower = min.move2Origin(self.radius, self.radius);
        const upper = max.moveAwayOrigin(self.radius, self.radius);

        return AABB.init(lower, upper);
    }
};

/// A solid capsule can be viewed as two semicircles connected
/// by a rectangle.
pub const Capsule = struct {
    /// Local center of the first semicircle
    center1: Point,
    /// Local center of the second semicircle
    center2: Point,
    /// The radius of the semicircles
    radius: f32,

    pub fn init(center1: Point, center2: Point, radius: f32) Capsule {
        return Capsule{
            .center1 = center1,
            .center2 = center2,
            .radius = radius,
        };
    }

    pub fn computeAABB(self: *Capsule, transform: *Point.Transform) AABB {
        var p1 = self.center1.transform2World(transform);
        var p2 = self.center2.transform2World(transform);

        var min = p1.newMinPoint(&p2);
        var max = p1.newMaxPoint(&p2);

        const lower = min.move2Origin(self.radius, self.radius);
        const upper = max.moveAwayOrigin(self.radius, self.radius);

        return AABB.init(lower, upper);
    }
};

/// A solid circle
pub const Circle = struct {
    /// The local center
    center: Point,
    /// The radius
    radius: f32,

    pub fn init(center: Point, radius: f32) Circle {
        return Circle{
            .center = center,
            .radius = radius,
        };
    }

    pub fn computeAABB(self: *Circle, transform: *Point.Transform) AABB {
        var p = self.center.transform2World(transform);

        const lower = p.move2Origin(self.radius, self.radius);
        const upper = p.moveAwayOrigin(self.radius, self.radius);

        return AABB.init(lower, upper);
    }
};

/// This is used to filter collision on shapes. It affects shape-vs-shape collision
/// and shape-versus-query collision (such as WorldCastRay).
pub const Filter = struct {
    /// The collision category bits. Normally you would just set one bit. The category bits should
    /// represent your application object types. For example:
    /// enum MyCategories
    /// {
    ///    Static  = 0x00000001,
    ///    Dynamic = 0x00000002,
    ///    Debris  = 0x00000004,
    ///    Player  = 0x00000008,
    ///    // etc
    /// };
    category_bits: u64,

    /// The collision mask bits. This states the categories that this
    /// shape would accept for collision.
    /// For example, you may want your player to only collide with static objects
    /// and other players.
    /// maskBits = Static | Player;
    mask_bits: u64,

    /// Collision groups allow a certain group of objects to never collide (negative)
    /// or always collide (positive). A group index of zero has no effect. Non-zero group filtering
    /// always wins against the mask bits.
    /// For example, you may want ragdolls to collide with other ragdolls but you don't want
    /// ragdoll self-collision. In this case you would give each ragdoll a unique negative group index
    /// and apply that group index to all shapes on the ragdoll.
    group_index: u32,
};

/// Axis-aligned bounding box
pub const AABB = struct {
    lower_bound: Point,
    upper_bound: Point,

    pub fn init(lower_bound: Point, upper_bound: Point) AABB {
        return AABB{
            .lower_bound = lower_bound,
            .upper_bound = upper_bound,
        };
    }

    pub fn getCenter(self: *AABB) Point {
        return Point.init(0.5 * (self.lower_bound.x + self.upper_bound.x), 0.5 * (self.lower_bound.y + self.upper_bound.y));
    }
};

/// Shape type
pub const ShapeType = enum {
    /// A circle with an offset
    Circle,

    /// A capsule is an extruded circle
    Capsule,

    /// A line segment
    Segment,

    /// A convex polygon
    Polygon,

    /// A line segment owned by a chain shape
    ChainSegment,
};

/// The maximum number of vertices on a convex polygon. Changing this affects performance even if you
/// don't use more vertices.
pub const MaxPolygonVertices: u8 = 32;
